namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Text;
  /// <summary>
  /// EntryAddressType
  /// </summary>
  public enum EntryAddressType
  {
    Invalid       = 0,
    ExportRVA     = 1,
    ForwarderRVA  = 2,
  }
  /// <summary>
  /// ExportAddress
  /// </summary>
  public struct ExportAddress
  {
    #region Statics
    public static implicit operator UInt32(ExportAddress exportAddress)
    {
      return exportAddress.EntryAddress;
    }
    public static implicit operator EntryAddressType(ExportAddress exportAddress)
    {
      return exportAddress.Type;
    }
    public static ExportAddress it()
    {
      return new ExportAddress();
    }
    public static ExportAddress of(EntryAddressType type, UInt32 entryAddress)
    {
      return new ExportAddress(type, entryAddress);
    }
    #endregion
    #region Fields
    public EntryAddressType Type;
    public UInt32 EntryAddress;
    #endregion
    #region Constructors
    public ExportAddress(EntryAddressType type, UInt32 entryAddress)
    {
      this.Type = type;
      this.EntryAddress = entryAddress;
    }
    #endregion
  }

  //public struct ExportEntry
  //{
  //  #region Fields
  //  public UInt32 VirtualAddress;
  //  public UInt16 OrdinalNumber;
  //  public UInt32 NameAddress;
  //  #endregion
  //}


  /// <summary>
  /// ExportDirectory
  /// </summary>
  public struct ExportDirectory : DataDirectory
  {
    #region Statics
    public static ExportDirectory it()
    {
      return new ExportDirectory();
    }
    public static ExportDirectory from(BinaryReader reader,NTHeader header)
    {
      return (ExportDirectory)it().Read(reader,header);
    }
    #endregion
    #region Consts
    public const UInt32 DefaultCharacteristics = 0;
    public const UInt32 DefaultTimeDateStamp = 0;
    public const UInt32 DefaultMajorVersion = 0;
    public const UInt32 DefaultMinorVersion = 0;
    public const UInt32 DefaultNameAddress = 0;
    public const UInt32 DefaultBase = 1;
    public const UInt32 DefaultNumberOfFunctions = 0;
    public const UInt32 DefaultNumberOfNames = 0;
    public const UInt32 DefaultAddressOfFunctions = 0;
    public const UInt32 DefaultAddressOfNames = 0;
    public const UInt32 DefaultAddressOfNameOrdinals = 0;
    #endregion
    #region Fields
    /// <summary>
    /// A reserved field, set to zero for now.
    /// </summary>
    public UInt32 Characteristics;
    /// <summary>
    /// Time and date the export data was created.
    /// </summary>
    public UInt32 TimeDateStamp;
    /// <summary>
    /// Major version number. The major/minor version number can be set by the user.
    /// </summary>
    public UInt16 MajorVersion;
    /// <summary>
    /// Minor version number.
    /// </summary>
    public UInt16 MinorVersion;
    /// <summary>
    /// Address of the ASCII string containing the name of the DLL. Relative to image base.
    /// </summary>
    public UInt32 NameAddress;
    /// <summary>
    /// Starting ordinal number for exports in this image. 
    /// This field specifies the starting ordinal number for the Export Address Table.
    /// Usually set to 1.
    /// </summary>
    public UInt32 Base;
    /// <summary>
    /// Number of entries in the Export Address Table.
    /// </summary>
    public UInt32 NumberOfFunctions;
    /// <summary>
    /// Number of entries in the Name Pointer Table 
    /// (also the number of entries in the Ordinal Table).
    /// </summary>
    public UInt32 NumberOfNames;
    /// <summary>
    /// Address of the Export Address Table, relative to the image base.
    /// </summary>
    public UInt32 AddressOfFunctions;     // RVA from base of image
    /// <summary>
    /// Address of the Export Name Pointer Table, relative to the image base. 
    /// The table size is given by Number of Name Pointers.
    /// </summary>
    public UInt32 AddressOfNames;         // RVA from base of image
    /// <summary>
    /// Address of the Ordinal Table, relative to the image base.
    /// </summary>
    public UInt32 AddressOfNameOrdinals;  // RVA from base of image
    #endregion
    #region Internal Fields
    internal ImageDataDirectory _Entry;
    internal string _Name;
    internal UInt32[] _Exports;
    internal UInt32[] _ExportNameRVAs;
    internal string[] _ExportNames;
    internal UInt16[] _NameOrdinals;
    #endregion
    #region Properties
    public ImageDataDirectory Entry
    {
      get
      {
        return this._Entry;
      }
      set
      {
        this._Entry = value;
      }
    }
    public UInt32[] Exports
    {
      get
      {
        return this._Exports;
      }
      set
      {
        this._Exports = value;
        this.NumberOfFunctions = value == null ? (UInt32)0 : (UInt32)value.Length;
      }
    }
    public string[] ExportNames
    {
      get
      {
        return this._ExportNames;
      }
      set
      {
        this._ExportNames = value;
        this.NumberOfNames = value == null ? (UInt32)0 : (UInt32)value.Length;
        if (this.NumberOfNames!=0)
        {
          this._NameOrdinals = new UInt16[this.NumberOfNames];
        }else
        {
          this._NameOrdinals = null;
        }
      }
    }
    public UInt16[] NameOrdinals
    {
      get
      {
        return this._NameOrdinals;
      }
    }
    public bool HasExports
    {
      get
      {
        return this._Exports != null && this._Exports.Length > 0;
      }
    }
    public bool HasExportNames
    {
      get
      {
        return this._ExportNames != null && this._ExportNames.Length > 0;
      }
    }
    public bool HasExportOrdinals
    {
      get
      {
        return this._NameOrdinals != null && this._NameOrdinals.Length > 0;
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    #region Read/Write
    public DataDirectory Read(BinaryReader reader,NTHeader header)
    {
      if (reader != null)
      {
        this._Entry = header[DirectoryEntries.Export];

        Int64 current = Utils.GetPosition(reader);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(reader, start))
        {
          this.Characteristics = Utils.Read32(reader);
          this.TimeDateStamp = Utils.Read32(reader);
          this.MajorVersion = Utils.Read16(reader);
          this.MinorVersion = Utils.Read16(reader);
          this.NameAddress = Utils.Read32(reader);
          this.Base = Utils.Read32(reader);
          this.NumberOfFunctions = Utils.Read32(reader);
          this.NumberOfNames = Utils.Read32(reader);
          this.AddressOfFunctions = Utils.Read32(reader);
          this.AddressOfNames = Utils.Read32(reader);
          this.AddressOfNameOrdinals = Utils.Read32(reader);
          this._Name = Utils.ReadName(reader,Encoding.ASCII, header.GetFilePointer(this.NameAddress));
          this._Exports = this.ReadExports(reader, header.GetFilePointer(this.AddressOfFunctions), this.NumberOfFunctions);
          this._ExportNameRVAs = this.ReadNameRVAs(reader, header.GetFilePointer(this.AddressOfNames), this.NumberOfNames);
          this._ExportNames = this.ReadNames(reader, this._ExportNameRVAs,header);
          this._NameOrdinals = this.ReadOrdinals(reader, header.GetFilePointer(this.AddressOfNameOrdinals), this.NumberOfNames);
          Utils.SetPosition(reader, current);
        }
      }
      return this;
    }
    public DataDirectory Write(BinaryWriter writer, NTHeader header)
    {
      if (writer != null)
      {
        header[DirectoryEntries.Export] = this._Entry;

        Int64 current = Utils.GetPosition(writer);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(writer, start))
        {

          Utils.Write32(writer, this.Characteristics);
          Utils.Write32(writer, this.TimeDateStamp);
          Utils.Write16(writer, this.MajorVersion);
          Utils.Write16(writer, this.MinorVersion);
          Utils.Write32(writer, this.NameAddress);
          Utils.Write32(writer, this.Base);
          Utils.Write32(writer, this.NumberOfFunctions);
          Utils.Write32(writer, this.NumberOfNames);
          Utils.Write32(writer, this.AddressOfFunctions);
          Utils.Write32(writer, this.AddressOfNames);
          Utils.Write32(writer, this.AddressOfNameOrdinals);

          Utils.WriteName(writer, header.GetFilePointer(this.NameAddress), this._Name, Encoding.ASCII);
          this.WriteExports(writer, header.GetFilePointer(this.AddressOfFunctions), this._Exports);
          this.WriteNameRVAs(writer, header.GetFilePointer(this.AddressOfNames), this._ExportNameRVAs, header);
          this.WriteNames(writer, this._ExportNames,this._ExportNameRVAs,header);
          this.WriteOrdinals(writer, header.GetFilePointer(this.AddressOfNameOrdinals), this._NameOrdinals);
          Utils.SetPosition(writer, current);
        }
      }
      return this;
    }
    #endregion
    #region Exports
    public ExportAddress GetExport(Int16 ordinal)
    {
      if (this.HasExports && this.HasExportOrdinals)
      {
        for (UInt32 i = 0; i < this.NameOrdinals.Length; i++)
        {
          if (ordinal == this.NameOrdinals[i])
          {
            return this.WrapAddress(this.Exports[i]);
          }
        }
      }
      return ExportAddress.it();
    }
    public ExportAddress GetExport(string name)
    {
      if ((!string.IsNullOrEmpty(name)) && this.HasExports && this.HasExportNames)
      {
        for (UInt32 i = 0; i < this.ExportNames.Length; i++)
        {
          if (name == this.ExportNames[i])
          {
            return this.WrapAddress(this.Exports[i]);
          }
        }
      }
      return ExportAddress.it();
    }
    //public ExportAddress DefineExport(string name, Int16 ordinal, ExportAddress export)
    //{
    //  return this.DefineExport(name, ordinal, export.EntryAddress);
    //}
    //public ExportAddress DefineExport(string name, Int16 ordinal, UInt32 RVA)
    //{
    //  //TODO: sort name/emit names first
    //  return new ExportAddress();
    //}
    #endregion
    #region Internals
    internal UInt32[] ReadExports(BinaryReader reader, UInt32 filePointer, UInt32 count)
    {
      UInt32[] exports = null;
      if (reader != null && filePointer != 0)
      {
        Int64 current = Utils.GetPosition(reader);
        if (Utils.SetPosition(reader, filePointer))
        {
          exports = new UInt32[count];
          for (UInt32 i = 0; i < count; i++)
          {
            exports[i] = Utils.Read32(reader);
          }
          Utils.SetPosition(reader, current);
        }
      }
      return exports;
    }
    internal UInt32[] ReadNameRVAs(BinaryReader reader, UInt32 filePointer, UInt32 count)
    {
      UInt32[] nameRVAs = null;
      if (reader != null && filePointer != 0)
      {
        Int64 current = Utils.GetPosition(reader);
        if (Utils.SetPosition(reader, filePointer))
        {
          nameRVAs = new UInt32[count];
          for (UInt32 i = 0; i < count; i++)
          {
            nameRVAs[i] = Utils.Read32(reader);
          }
          Utils.SetPosition(reader, current);
        }
      }
      return nameRVAs;
    }
    internal string[] ReadNames(BinaryReader reader, UInt32[] nameRVAs,NTHeader header)
    {
      string[] names = null;
      if (reader != null  && nameRVAs != null)
      {
        names = new string[nameRVAs.Length];
        for (UInt32 i = 0; i < names.Length; i++)
        {
          names[i] = Utils.ReadName(reader, Encoding.ASCII, header.GetFilePointer(nameRVAs[i]));
        }
      }
      return names;
    }
    internal UInt16[] ReadOrdinals(BinaryReader reader, UInt32 filePointer, UInt32 count)
    {
      UInt16[] ordinals = null;
      if (reader != null && filePointer != 0)
      {
        Int64 current = Utils.GetPosition(reader);
        if (Utils.SetPosition(reader, filePointer))
        {
          ordinals = new UInt16[count];
          for (UInt32 i = 0; i < count; i++)
          {
            ordinals[i] = Utils.Read16(reader);
          }
          Utils.SetPosition(reader, current);
        }
      }
      return ordinals;
    }
    internal UInt32[] WriteExports(BinaryWriter writer, UInt32 filePointer, UInt32[] exports)
    {

      if (writer != null && filePointer != 0 && exports != null)
      {
        Int64 current = Utils.GetPosition(writer);
        if (Utils.SetPosition(writer, filePointer))
        {
          for (UInt32 i = 0; i < exports.Length; i++)
          {
            Utils.Write32(writer,exports[i]);

          }
          Utils.SetPosition(writer, current);
        }
      }
      return exports;
    }
    internal UInt32[] WriteNameRVAs(BinaryWriter writer, UInt32 filePointer, UInt32[] nameRVAs, NTHeader header)
    {
      if (writer != null && nameRVAs != null)
      {
        Int64 current = Utils.GetPosition(writer);
        if (Utils.SetPosition(writer, filePointer))
        {
          for (UInt32 i = 0; i < nameRVAs.Length; i++)
          {
            Utils.Write32(writer, nameRVAs[i]);
          }
          Utils.SetPosition(writer, current);
        }
      }
      return nameRVAs;
    }
    internal string[] WriteNames(BinaryWriter writer, string[] names,UInt32[] nameRVAs, NTHeader header)
    {
      if (writer != null  && names != null && nameRVAs != null && names.Length == nameRVAs.Length)
      {
        for (UInt32 i = 0; i < names.Length; i++)
        {
          Utils.WriteName(writer, header.GetFilePointer(nameRVAs[i]), names[i], Encoding.ASCII);
        }
      }
      return names;
    }
    internal UInt16[] WriteOrdinals(BinaryWriter writer, UInt32 filePointer, UInt16[] ordinals)
    {
      if (writer != null && filePointer != 0 && ordinals!=null) 
      {
        Int64 current = Utils.GetPosition(writer);
        if (Utils.SetPosition(writer, filePointer))
        {
          for (UInt32 i = 0; i < ordinals.Length; i++)
          {
            Utils.Write16(writer,ordinals[i]);
          }
          Utils.SetPosition(writer, current);
        }
      }
      return ordinals;
    }
    internal bool IsValidExportAddress(UInt32 RVA)
    {
      return (RVA >= this._Entry.VirtualAddress) &&
        RVA < (this._Entry.VirtualAddress + this._Entry.VirtualSize);
    }

    internal ExportAddress WrapAddress(UInt32 RVA)
    {
      return ExportAddress.of(this.IsValidExportAddress(RVA) ? EntryAddressType.ExportRVA : EntryAddressType.ForwarderRVA, RVA);
    }
    #endregion
    #endregion
  }
}
